package org.aceor.mddal.group.selector;

import org.aceor.mddal.common.utils.ExceptionUtil;
import org.aceor.mddal.group.DataSourceWrapper;

import javax.sql.DataSource;
import java.sql.SQLException;
import java.util.List;
import java.util.Map;

/**
 * 对等数据库选择器。
 * 在数据完全相同的一组库中选择一个库
 * 用于对HA/RAC情况,多个读库中取一个读的操作
 *
 * @author linxuan
 */
public interface DBSelector {

    /**
     * 对等数据库选择器。
     * 在数据完全相同的一组库中选择一个库
     * 用于对HA/RAC情况,多个读库中取一个读的操作
     */
    DataSource select();

    /**
     * 返回指定dsKey对应的数据源。若对应数据源的当前权重为0，则返回null
     * 这个方法同时可以用来判断一个dsKey对应的库是否可读或可写：
     * rselector.get(wBaseDsKey) != null 则可读
     * wselector.get(rBaseDsKey) != null 则可写
     * TGroupConnection读写连接复用的旧实现会用到这个功能
     *
     * @param dsKey 内部和每一个物理DataSource对应的key, 在初始化dbSelector时指定
     * @return 返回dsKey对应的数据源
     */
    DataSource get(String dsKey);

    /**
     * 以选择到的DataSource和传入的args，重试执行
     * tryer.tryOnDataSource(String dsKey, DataSource ds, Object... args)
     * 每次选择DataSource会排除上次重试失败的, 直到达到指定的重试次数，或期间抛出非数据库不可用异常(MySQLExceptionSorter.isExceptionFatal判断)
     * <p>
     * 抛出异常后，以历次重试异常列表，和最初的args，调用
     * tryer.onSQLException(List<SQLException> exceptions, Object... args)
     *
     * @param tryer 重试的回调接口.重试失败一个,会继续重试下一个,直到重试完所有或到达重试次数
     * @param times 重试次数
     * @param args
     * @throws SQLException
     */
    <T> T tryExecute(DataSourceTryer<T> tryer, int times, Object... args) throws SQLException;

    /**
     * @param failedDataSources: 在调用该方法前，已经得知试过失败的DataSource和对应的SQLException
     *                           存在这个参数的原因，是因为数据库操作割裂为getConnection/createStatement/execute几步，而并不是在一个大的try catch中
     *                           failedDataSources == null 表示不需要重试，遇到任何异常直接抛出。如在写库上的操作
     */
    <T> T tryExecute(Map<DataSource, SQLException> failedDataSources, DataSourceTryer<T> tryer, int times,
                     Object... args) throws SQLException;

    /**
     * 区分写 or 读 数据源
     *
     * @param readable
     */
    void setReadable(boolean readable);

    /**
     * 在DBSelector管理的数据源上重试执行操作的回调接口
     */
    interface DataSourceTryer<T> {
        /**
         * tryExecute中重试调用tryOnDataSource遇到非数据库不可用异常，或用完重试次数时，会调用该方法
         *
         * @param exceptions 历次重试失败抛出的异常。
         *                   最后一个异常可能是数据库不可用的异常，也可能是普通的SQL异常
         *                   最后一个之前的异常是数据库不可用的异常
         * @param args       与tryOnDataSource时的args相同，都是用户调用tryExecute时传入的arg
         * @return
         * @throws SQLException
         */
        T onSQLException(List<SQLException> exceptions, Object... args)
                throws SQLException;

        T tryOnDataSource(DataSourceWrapper dsw, Object... args) throws SQLException;
    }

    public static abstract class AbstractDataSourceTryer<T> implements DataSourceTryer<T> {
        @SuppressWarnings("unchecked")
        public T onSQLException(List<SQLException> exceptions, Object... args)
                throws SQLException {
            //简单抛出即可
            ExceptionUtil.throwAllSQLExceptions(exceptions);
            return null;
        }
    }
}
